package com.trade.system.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.vote.AffirmativeBased;
import org.springframework.security.access.vote.RoleVoter;
import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.access.expression.WebExpressionVoter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import java.util.ArrayList;
import java.util.List;

/**
 * Class Description: 權限配置類
 *
 * @Version v1.0 2017/4/21 22:52
 * @JDK version 1.8.0_51
 * @Author zfb
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomUserDetailsService customUserDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        /*
          Spring提供了3个决策管理器，至于这三个管理器是如何工作的请查看SpringSecurity源码
            AffirmativeBased 一票通过，只要有一个投票器通过就允许访问
            ConsensusBased 有一半以上投票器通过才允许访问资源
            UnanimousBased 所有投票器都通过才允许访问
         */
        //定义管理器
        List<AccessDecisionVoter<?>> accessDecisionVoters = new ArrayList<AccessDecisionVoter<?>>();
        accessDecisionVoters.add(new RoleVoter());
        accessDecisionVoters.add(new WebExpressionVoter());
        AffirmativeBased accessDecisionManager = new AffirmativeBased(accessDecisionVoters);
        //禁用csrf：
        //CSRF是用于防止跨域攻击的，在security3中是默认关闭的，但是在security4中默认开启。
        //开启之后在登陆时候的form中要加一个input放csrf的token
        //<input type="text" name="${_csrf.parameterName}" id=""  value="${_csrf.token}"/>
        //这样表单提交之后才能正常验证。不加的话会一直返回403，显示Access Denied。
        /*
         * 之前和朋友交流的时候他因为是用spring-boot搭建的环境，不知道怎么回事他的表单中没有这个值也可以，
         * 在request中会自动加入这个值。有人知道是怎么回事吗？
         * (2017年2月23日更：查了资料说如果用springMVC的form标签生成的form或者用thymeleaf模板生成的
         * 表单提交的时候会自动加上这个东西。所以不需要显式的写。
         * springMVC的form标签没试过，朋友是用thymeleaf，所以他不用写。)
         */

        /*前端获取CsrfToken：
         *  Spring Srcurity开启csrf功能之后，所有post请求都要加上csrfToken，而且只要session不过期，
         *  这个token就不会变。在我们的应用中，除了表单之外，有些情况下我们需要用ajax去获取数据，
         *  而为了数据安全，又不得不使用post方法。这个时候如果请求的路径不在排除列表中，
         *  那就必须加上csrfToken了，但是这个token哪里来？其实，Spring Security已经帮我们传到前端了。
         *  这个Token是在request的attribute中，attribute名称为 _csrf。注意名称前有个下划线。
         *  只是这样还没完，这个获取到的是org.springframework.security.web.csrf.DefaultCsrfToken对象，
         *  我们要的是里面的token这个值。所以，如果是thymeleaf要获取的话，那就是：
         *  <input type="hidden" id="csrfToken" th:value="${#httpServletRequest.getAttribute('_csrf').token}" />
         */

        //禁用csrf
        http.csrf().disable()
                .authorizeRequests().antMatchers("/login", "/js/**", "/vendor/**", "/resource/**").permitAll()
                .anyRequest().authenticated().accessDecisionManager(accessDecisionManager)
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    public <O extends FilterSecurityInterceptor> O postProcess(
                            O fsi) {
                        fsi.setSecurityMetadataSource(new FilterSecurityMetadataSource());
                        return fsi;
                    }
                })
                .and()
                .formLogin()
                //指定登录页是”/login”
                .loginPage("/login")
                //默认登录成功页面
                .defaultSuccessUrl("/index")
                //默认登录失败页面
                .failureUrl("/login?error=1")
                .loginProcessingUrl("/j_login")
                .permitAll()
                //登录成功后可使用loginSuccessHandler()存储用户信息，可选。
                .successHandler(new LoginSuccessHandler())
                .and()
                .logout()
                //退出登录后的默认网址是”/login”
                .logoutSuccessUrl("/login")
                .permitAll()
                .invalidateHttpSession(true)
                .and()
                //登录后记住用户，下次自动登录
                //数据库中必须存在名为persistent_logins的表
                .rememberMe();
                //.tokenValiditySeconds(1209600)
                //指定记住登录信息所使用的数据源
                //.tokenRepository(tokenRepository());
    }


    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        //指定密码加密所使用的加密器为Md5PasswordEncoder
        //需要将密码加密后写入数据库
        //不删除凭据，以便记住用户
        auth.userDetailsService(customUserDetailsService).passwordEncoder(new Md5PasswordEncoder());
        //加密器为BCryptPasswordEncoder
        //auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());
        //不删除凭据，以便记住用户
        auth.eraseCredentials(false);
    }

    /*@Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(4);
    }*/

}
